using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Ltf.Remoting.TeamFoundation;
using Microsoft.TeamFoundation.VersionControl.Client;
using Ltf.Remoting.Operations;
using System.Xml;
using Ltf.Remoting.TFAccess;

namespace Ltf.Diffs
{
	public class DifferenceInfos : List<DifferenceInfo>
	{
		TFServerConnection connection;
		Workspace workspace;
		string changeFromPath;
		int versionFrom;
		string changeToPath;
		int versionTo;
		string targetPath;

		IDifRepository changeToRepo;
		IDifRepository changeFromRepo;
		IDifRepository targetRepo;
		DirDiffer dirDifferTo;
		DirDiffer dirDifferFrom;

		public DifferenceInfos()
		{
		}

		public DifferenceInfos(TFServerConnection connection, Workspace workspace, string changeFromPath, int versionFrom,
			string changeToPath, int versionTo, string targetPath, string diffFile, DirDiffer differResults, bool trackRenames)
		{
			this.connection = connection;
			this.workspace = workspace;
			this.changeFromPath = changeFromPath;
			this.changeToPath = changeToPath;
			this.targetPath = targetPath;
			this.versionFrom = versionFrom;
			this.versionTo = versionTo;

			changeToRepo = LizardFileAccess.GetRepositoryForPath(connection, changeToPath, versionTo.ToString());
			changeFromRepo = LizardFileAccess.GetRepositoryForPath(connection, changeFromPath, versionFrom.ToString());
			targetRepo = LizardFileAccess.GetRepositoryForPath(connection, targetPath, string.Empty);

			dirDifferTo = new DirDiffer(changeToRepo, targetRepo, changeToPath, targetPath, string.Empty, false, false, false, false, false);
			dirDifferFrom = new DirDiffer(changeFromRepo, targetRepo, changeToPath, targetPath, string.Empty, false, false, false, false, false);


			List<string> oldNames = new List<string>();
			List<string> newNames = new List<string>();

			if (trackRenames)
			{
				string[][] renames = GetRenames();
				oldNames = new List<string>(renames[1]);
				newNames = new List<string>(renames[0]);

			}

			List<string> renamesDone = new List<string>();

			AddChangedFiles(diffFile, oldNames, newNames, renamesDone);

			AddRenameOnlys(oldNames, newNames, renamesDone);

			AddNewFiles(differResults, oldNames, newNames, renamesDone);

			//TODO: we've done changes, renames, moves and added files now, just deletions to take of (I hope).
			AddDeletedFiles(differResults, oldNames);

		}

		private void AddChangedFiles(string diffFile, List<string> oldNames, List<string> newNames, List<string> renamesDone)
		{
			DifferenceInfo di = null;
			StringReader sr = new StringReader(diffFile);
			string oldName;
			string newName;

			string line = sr.ReadLine();
			string[] paths = new string[0];
			if (line != null)
				paths = GetDiffPaths(line);
			StringBuilder sb = new StringBuilder();
			while (line != null)
			{
				line = sr.ReadLine();
				if (line != null)
				{
					if (line.StartsWith("diff "))
					{
						oldName = LizardFileAccess.GetPathEnd(changeFromPath, paths[1]);
						if (oldNames.Contains(paths[1]))
							newName = LizardFileAccess.GetPathEnd(changeToPath, newNames[oldNames.IndexOf(paths[1])]);
						else
							newName = string.Empty;

						string fullOldName = LizardFileAccess.PathConcat(targetPath, oldName);
						string fullNewName = LizardFileAccess.PathConcat(targetPath, newName);
						bool isRenameExisting = File.Exists(fullNewName) && !File.Exists(fullOldName);


						di = new DifferenceInfo(newName, oldName, FileChanges.Content, changeFromPath, changeToPath, targetPath, sb.ToString());
						if (isRenameExisting)
						{
							di.MergeCommands.Add(new MergeCommands.MergeFileChangesCommand(connection, fullNewName, sb.ToString(), (di.Conflicts > 0)));
						}
						else
						{
							di.MergeCommands.Add(new MergeCommands.MergeFileChangesCommand(connection, fullOldName, sb.ToString(), (di.Conflicts > 0)));
							if (newName != string.Empty)
							{
								di.MergeCommands.Add(new MergeCommands.MergeRenameFileCommand(connection, fullOldName, fullNewName));
								di.FileChanges = (di.FileChanges | FileChanges.Path);
							}
						}
						this.Add(di);
						//						this.Add(new DifferenceInfo(newName, oldName, DirDiffer.DifferenceType.FileDiff, paths[1], paths[2], paths[0], sb.ToString()));
						paths = GetDiffPaths(line);
						sb = new StringBuilder();
					}
					sb.AppendLine(line);
				}
			}
			if (paths.Length > 0)
			{
				oldName = LizardFileAccess.GetPathEnd(changeFromPath, paths[1]);
				if (oldNames.Contains(paths[1]))
					newName = LizardFileAccess.GetPathEnd(changeToPath, newNames[oldNames.IndexOf(paths[1])]);
				else
					newName = string.Empty;


				di = new DifferenceInfo(newName, oldName, FileChanges.Content, changeFromPath, changeToPath, targetPath, sb.ToString());
				if (di.AllChanges != 0)
					di.MergeCommands.Add(new MergeCommands.MergeFileChangesCommand(connection, LizardFileAccess.PathConcat(targetPath, oldName), sb.ToString(), (di.Conflicts > 0)));
				if (newName != string.Empty)
				{
					di.MergeCommands.Add(new MergeCommands.MergeRenameFileCommand(connection, LizardFileAccess.PathConcat(targetPath, oldName), LizardFileAccess.PathConcat(targetPath, newName)));
					di.FileChanges = (di.FileChanges | FileChanges.Path);
				}
				this.Add(di);
			}
			foreach (DifferenceInfo diffInfo in this)
			{
				diffInfo.BuildCounts(oldNames, newNames, renamesDone);
			}
		}

		private void AddRenameOnlys(List<string> oldNames, List<string> newNames, List<string> renamesDone)
		{
			string oldName;
			string newName;
			for (int idx = 0; idx < oldNames.Count; idx++)
			{
				string fullOldName = oldNames[idx];
				string fullNewName = newNames[idx];
				if (!renamesDone.Contains(oldNames[idx]))
				{

					oldName = LizardFileAccess.GetPathEnd(changeFromPath, oldNames[idx]);
					if (newNames[idx].StartsWith("$"))
						newName = LizardFileAccess.GetPathEnd(workspace.GetServerItemForLocalItem(changeToPath), newNames[idx]);
					else
						newName = LizardFileAccess.GetPathEnd(changeToPath, newNames[idx]);

					fullNewName = LizardFileAccess.PathConcat(changeToPath, newName);

					renamesDone.Add(fullNewName);

					string changeToName = LizardFileAccess.PathConcat(changeToPath, newName);

					string targetOldName = LizardFileAccess.PathConcat(targetPath, oldName);
					string targetNewName = LizardFileAccess.PathConcat(targetPath, newName);

					if (changeFromRepo.FileExists(fullOldName))
					{
						DifferenceInfoRenameType renameType = (Path.GetDirectoryName(targetOldName) == Path.GetDirectoryName(targetNewName)) ? DifferenceInfoRenameType.Renamed : DifferenceInfoRenameType.Moved;
						string itemTargetPath = targetPath + "\\" + oldName.Replace("/", "\\");

						if (!targetRepo.FileExists(targetNewName))
						{
							DifferenceInfo di = new DifferenceInfo(newName, oldName, FileChanges.Path, changeFromPath, changeToPath, targetPath, string.Empty);
							di.MergeCommands.Add(new MergeCommands.MergeRenameFileCommand(connection, LizardFileAccess.PathConcat(targetPath, oldName), LizardFileAccess.PathConcat(targetPath, newName)));
							di.RenameType = renameType;
							di.Changes = -1;
							this.Add(di);
						}
						else  //if (targetRepo.FileExists(targetNewName))
						{
							bool isDifferent = false;
							DirDiffer.DifferenceType dt = dirDifferTo.FullFileDiff(ref isDifferent, changeToName, targetNewName, false);
							isDifferent = (dt == DirDiffer.DifferenceType.FileDiff);
							renameType = (Path.GetDirectoryName(targetOldName) == Path.GetDirectoryName(targetNewName)) ? DifferenceInfoRenameType.Renamed : DifferenceInfoRenameType.Moved;
							itemTargetPath = targetPath + "\\" + oldName.Replace("/", "\\");

							DifferenceInfo di = new DifferenceInfo(newName, oldName, FileChanges.Path, changeFromPath, changeToPath, targetPath, string.Empty);
							di.RenameType = renameType;
							if (isDifferent)
							{
								di.Conflicts = -1;
								di.FileChanges = (di.FileChanges | FileChanges.Conflicts);
							}
							else
								di.AllExisting = -1;
							this.Add(di);
						}
					}

				}
			}
		}

		private void AddNewFiles(DirDiffer differResults, List<string> oldNames, List<string> newNames, List<string> renamesDone)
		{
			foreach (XmlNode node in differResults.Results.SelectNodes("//FileAdded | // SameFileAdded"))
			{
				string newFile = node.Attributes.GetNamedItem("FileFullName").Value;
				if (newFile.StartsWith("\\") || newFile.StartsWith("/"))
					newFile = newFile.Substring(1);

				string newPath = LizardFileAccess.PathConcat(changeToPath, newFile);

				// this might not really be a new file, it might just be a rename, in which case it should have
				// already been processed.
				if (!renamesDone.Contains(newPath))
				{

					// we have to test if whether the the target file exists, because if it does
					// it might be different and conflict. 

					bool isDifferent = false;
					bool isExisting = false;
					string newTargetFile = targetPath;
					if (changeToRepo.FileExists(newPath))
					{
						newTargetFile = LizardFileAccess.PathConcat(newTargetFile, newFile);

						if (targetRepo.FileExists(newTargetFile))
						{
							DirDiffer.DifferenceType dt = dirDifferTo.FullFileDiff(ref isDifferent, newPath, newTargetFile, false);
							isDifferent = (dt == DirDiffer.DifferenceType.FileDiff);
							isExisting = true;
						}
					}

					DifferenceInfo di = new DifferenceInfo(newFile, newFile, FileChanges.Added, changeFromPath, changeToPath, targetPath, string.Empty);
					di.ItemTargetPath = newPath;
					di.Inserts = -1;
					if (isDifferent)
					{
						di.Conflicts = -1;
						di.FileChanges = (di.FileChanges | FileChanges.Conflicts);
					}
					else if (isExisting)
						di.AllExisting = -1;
					else
						di.MergeCommands.Add(new MergeCommands.MergeAddFileCommand(connection, LizardFileAccess.PathConcat(targetPath, newFile), newPath));

					this.Add(di);
				}
			}
		}

		private void AddDeletedFiles(DirDiffer differResults, List<string> oldNames)
		{
			foreach (XmlNode node in differResults.Results.SelectNodes("//FileMissing"))
			{
				string oldFile = node.Attributes.GetNamedItem("FileFullName").Value;
				if (oldFile.StartsWith("\\") || oldFile.StartsWith("/"))
					oldFile = oldFile.Substring(1);

				string oldPath = LizardFileAccess.PathConcat(changeFromPath, oldFile);

				// we have to test if whether the the target file exists, because if it does
				// it might be different and conflict. 
				if (!oldNames.Contains(oldPath))
				{
					bool isDifferent = false;
					string oldTargetFile = LizardFileAccess.PathConcat(targetPath, oldFile);

					if (targetRepo.FileExists(oldTargetFile))
					{
						DirDiffer.DifferenceType dt = dirDifferFrom.FullFileDiff(ref isDifferent, oldPath, oldTargetFile, false);
						isDifferent = (dt == DirDiffer.DifferenceType.FileDiff);
					}

					DifferenceInfo di = new DifferenceInfo(string.Empty,oldFile, FileChanges.Deleted, changeFromPath, changeToPath, targetPath, string.Empty);
					di.ItemTargetPath = oldPath;
					di.Deletes = -1;
					if (isDifferent)
					{
						di.Conflicts = -1;
						di.FileChanges = (di.FileChanges | FileChanges.Conflicts);
					}
					else
						di.MergeCommands.Add(new MergeCommands.MergeDeleteFileCommand(connection, oldTargetFile));
					this.Add(di);
				}
			}
		}

		private string[][] GetRenames()
		{
			WorkspaceOperators wso = new WorkspaceOperators(connection, workspace);
			string p1 = changeFromPath;
			if (!p1.StartsWith("$"))
				p1 = workspace.TryGetServerItemForLocalItem(p1);

			bool includePends = (!changeToPath.StartsWith("$"));

			string[][] renames = wso.GetRenames(p1, versionFrom, versionTo, includePends);
			return renames;
		}



		private string[] GetDiffPaths(string diffDescription)
		{
			string[] parts = diffDescription.Split(new string[] { " \"" }, StringSplitOptions.None);
			if (parts.Length == 4)
			{
				string path1 = parts[1].Substring(0, parts[1].Length - 1);
				string path2 = parts[2].Substring(0, parts[2].Length - 1);
				string path3 = parts[3].Substring(0, parts[3].Length - 2);
				return new string[] { path1, path2, path3 };
			}
			else
				return new string[0];
		}


	}
}
